@@ -43,12 +43,29 @@ module LiquidInterpolatable |
||
43 | 43 |
end |
44 | 44 |
|
45 | 45 |
require 'uri' |
46 |
- # Percent encoding for URI conforming to RFC 3986. |
|
47 |
- # Ref: http://tools.ietf.org/html/rfc3986#page-12 |
|
48 | 46 |
module Filters |
47 |
+ # Percent encoding for URI conforming to RFC 3986. |
|
48 |
+ # Ref: http://tools.ietf.org/html/rfc3986#page-12 |
|
49 | 49 |
def uri_escape(string) |
50 | 50 |
CGI.escape(string) rescue string |
51 | 51 |
end |
52 |
+ |
|
53 |
+ # Escape a string for use in XPath expression |
|
54 |
+ def to_xpath(string) |
|
55 |
+ subs = string.scan(/\G(?:\A\z|[^"]+|[^']+)/).map { |x| |
|
56 |
+ case x |
|
57 |
+ when /"/ |
|
58 |
+ %Q{'#{x}'} |
|
59 |
+ else |
|
60 |
+ %Q{"#{x}"} |
|
61 |
+ end |
|
62 |
+ } |
|
63 |
+ if subs.size == 1 |
|
64 |
+ subs.first |
|
65 |
+ else |
|
66 |
+ 'concat(' << subs.join(', ') << ')' |
|
67 |
+ end |
|
68 |
+ end |
|
52 | 69 |
end |
53 | 70 |
Liquid::Template.register_filter(LiquidInterpolatable::Filters) |
54 | 71 |
|
@@ -1,4 +1,5 @@ |
||
1 | 1 |
require 'spec_helper' |
2 |
+require 'nokogiri' |
|
2 | 3 |
|
3 | 4 |
describe LiquidInterpolatable::Filters do |
4 | 5 |
before do |
@@ -36,4 +37,21 @@ describe LiquidInterpolatable::Filters do |
||
36 | 37 |
agent.errors[:options].first.should =~ /not properly terminated/ |
37 | 38 |
end |
38 | 39 |
end |
40 |
+ |
|
41 |
+ describe 'to_xpath' do |
|
42 |
+ before do |
|
43 |
+ def @filter.to_xpath_roundtrip(string) |
|
44 |
+ Nokogiri::XML('').xpath(to_xpath(string)) |
|
45 |
+ end |
|
46 |
+ end |
|
47 |
+ |
|
48 |
+ it 'should escape a string for use in XPath expression' do |
|
49 |
+ [ |
|
50 |
+ %q{abc}.freeze, |
|
51 |
+ %q{'a"bc'dfa""fds''fa}.freeze, |
|
52 |
+ ].each { |string| |
|
53 |
+ @filter.to_xpath_roundtrip(string).should == string |
|
54 |
+ } |
|
55 |
+ end |
|
56 |
+ end |
|
39 | 57 |
end |
@@ -456,7 +456,10 @@ fire: hot |
||
456 | 456 |
before do |
457 | 457 |
@event = Event.new |
458 | 458 |
@event.agent = agents(:bob_rain_notifier_agent) |
459 |
- @event.payload = { 'url' => "http://xkcd.com" } |
|
459 |
+ @event.payload = { |
|
460 |
+ 'url' => 'http://xkcd.com', |
|
461 |
+ 'link' => 'Random', |
|
462 |
+ } |
|
460 | 463 |
end |
461 | 464 |
|
462 | 465 |
it "should scrape from the url element in incoming event payload" do |
@@ -467,17 +470,25 @@ fire: hot |
||
467 | 470 |
end |
468 | 471 |
|
469 | 472 |
it "should interpolate values from incoming event payload" do |
470 |
- @event.payload['title'] = 'XKCD' |
|
471 |
- |
|
472 | 473 |
lambda { |
473 |
- @valid_options['extract']['site_title'] = { |
|
474 |
- 'css' => "#comic img", 'value' => "'{{title}}'" |
|
474 |
+ @valid_options['extract'] = { |
|
475 |
+ 'from' => { |
|
476 |
+ 'xpath' => '*[1]', |
|
477 |
+ 'value' => '{{url | to_xpath}}' |
|
478 |
+ }, |
|
479 |
+ 'to' => { |
|
480 |
+ 'xpath' => '(//a[@href and text()={{link | to_xpath}}])[1]', |
|
481 |
+ 'value' => '@href' |
|
482 |
+ }, |
|
475 | 483 |
} |
476 | 484 |
@checker.options = @valid_options |
477 | 485 |
@checker.receive([@event]) |
478 | 486 |
}.should change { Event.count }.by(1) |
479 | 487 |
|
480 |
- Event.last.payload['site_title'].should == 'XKCD' |
|
488 |
+ Event.last.payload.should == { |
|
489 |
+ 'from' => 'http://xkcd.com', |
|
490 |
+ 'to' => 'http://dynamic.xkcd.com/random/comic/', |
|
491 |
+ } |
|
481 | 492 |
end |
482 | 493 |
end |
483 | 494 |
end |